Switch to using a systemd generator for /var
authorColin Walters <walters@verbum.org>
Thu, 11 May 2017 18:54:12 +0000 (14:54 -0400)
committerAtomic Bot <atomic-devel@projectatomic.io>
Tue, 16 May 2017 16:13:05 +0000 (16:13 +0000)
If one wants to set up a mount for `/var` in `/etc/fstab`, it
won't be mounted since `ostree-prepare-root` set up a bind mount for
`/var` to `/sysroot/ostree/$stateroot/var`, and systemd will take
the already extant mount over what's in `/etc/fstab`.

There are a few options to fix this, but what I settled on is parsing
`/etc/fstab` in a generator (exactly like `systemd-fstab-generator` does),
except here we look for an explicit mount for `/var`, and if one *isn't* found,
synthesize the default ostree mount to the stateroot. Another nice property is
that if an admin creates a `var.mount` unit in `/etc` for example, that will
also override our mount.

Note that today ostree doesn't hard depend on systemd, so this behavior only
kicks in if we're built with systemd *and* libmount support (for parsing
`/etc/fstab`).  I didn't really test that case though.

Initially I started writing this as a "pure libc" program, but at one point
decided to use `libostree.so` to find the booted deployment. That didn't work
out because `/boot` wasn't necessarily mounted and hence we couldn't find the
bootloader config. A leftover artifact from this is that the generator code
calls into libostree via the "cmd private" infrastructure. But it's an easy way
to share code, and doesn't hurt.

Closes: #859
Approved by: jlebon

Makefile-libostree.am
Makefile-switchroot.am
configure.ac
src/libostree/ostree-cmdprivate.c
src/libostree/ostree-cmdprivate.h
src/libostree/ostree-impl-system-generator.c [new file with mode: 0644]
src/libostree/ostree-sysroot-deploy.c
src/switchroot/ostree-mount-util.h
src/switchroot/ostree-prepare-root.c
src/switchroot/ostree-system-generator.c [new file with mode: 0644]

index 86ba0414f846c2512eed6f36f2cca77efa71218e..7f2e2a4a28628ac8db5e99324740548cf1ac3778 100644 (file)
@@ -115,6 +115,7 @@ libostree_1_la_SOURCES = \
        src/libostree/ostree-sysroot-cleanup.c \
        src/libostree/ostree-sysroot-deploy.c \
        src/libostree/ostree-sysroot-upgrader.c \
+       src/libostree/ostree-impl-system-generator.c \
        src/libostree/ostree-bootconfig-parser.c \
        src/libostree/ostree-deployment.c \
        src/libostree/ostree-bootloader.h \
index 6fd2f82075ec284e7e5147d49ec9c599efe8336b..dd24010e18714ee003afaa11bc862711bf17c792 100644 (file)
@@ -27,6 +27,7 @@ ostree_prepare_root_SOURCES = \
     src/switchroot/ostree-mount-util.h \
     src/switchroot/ostree-prepare-root.c \
     $(NULL)
+ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS)
 
 if BUILDOPT_USE_STATIC_COMPILER
 # ostree-prepare-root can be used as init in a system without a populated /lib.
@@ -45,7 +46,6 @@ ostree-prepare-root : $(ostree_prepare_root_SOURCES)
        $(STATIC_COMPILER) -o $@ -static $(ostree_prepare_root_SOURCES) $(AM_CPPFLAGS) $(AM_CFLAGS) $(DEFAULT_INCLUDES)
 else
 ostree_boot_PROGRAMS += ostree-prepare-root
-
 ostree_prepare_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
 endif
 
@@ -53,4 +53,19 @@ ostree_remount_SOURCES = \
     src/switchroot/ostree-mount-util.h \
     src/switchroot/ostree-remount.c \
     $(NULL)
-ostree_remount_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
+ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) -Isrc/switchroot
+
+# This is the "new mode" of using a generator for /var; see
+# https://github.com/ostreedev/ostree/issues/855
+if BUILDOPT_SYSTEMD_AND_LIBMOUNT
+ostree_prepare_root_CPPFLAGS += -DHAVE_SYSTEMD_AND_LIBMOUNT=1
+ostree_remount_CPPFLAGS += -DHAVE_SYSTEMD_AND_LIBMOUNT=1
+
+systemdsystemgenerator_PROGRAMS = ostree-system-generator
+GITIGNOREFILES += $(systemdsystemgenerator_PROGRAMS)
+ostree_system_generator_SOURCES = src/switchroot/ostree-mount-util.h \
+                                  src/switchroot/ostree-system-generator.c
+ostree_system_generator_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libostree
+ostree_system_generator_CFLAGS = $(AM_CFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS)
+ostree_system_generator_LDADD = $(AM_LDFLAGS) libglnx.la libostree-1.la $(OT_INTERNAL_GIO_UNIX_LIBS)
+endif
index 6acd0b3c77293293e2b11dbccdaa6832412dec0d..06d39126ff35c523c1690b6b439b5bba53aaa0a4 100644 (file)
@@ -389,8 +389,19 @@ AS_IF([test "x$have_libsystemd" = "xyes"], [
   AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [
     AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
   ])
+  AC_ARG_WITH([systemdsystemgeneratordir],
+              AS_HELP_STRING([--with-systemdsystemgeneratordir=DIR], [Directory for systemd generators]),
+              [],
+              [with_systemdsystemgeneratordir=$($PKG_CONFIG --variable=systemdsystemgeneratordir systemd)])
+  AS_IF([test "x$with_systemdsystemgeneratordir" != "xno"], [
+    AC_SUBST([systemdsystemgeneratordir], [$with_systemdsystemgeneratordir])
+  ])
 ])
 AM_CONDITIONAL(BUILDOPT_SYSTEMD, test x$with_systemd = xyes)
+dnl If we have both, we use the "new /var" model with ostree-system-generator
+AM_CONDITIONAL(BUILDOPT_SYSTEMD_AND_LIBMOUNT,[test x$with_systemd = xyes && test x$with_libmount = xyes])
+AM_COND_IF(BUILDOPT_SYSTEMD_AND_LIBMOUNT,
+  AC_DEFINE([BUILDOPT_LIBSYSTEMD_AND_LIBMOUNT], 1, [Define if systemd and libmount]))
 
 AC_ARG_WITH(builtin-grub2-mkconfig,
             AS_HELP_STRING([--with-builtin-grub2-mkconfig],
index 4367b497e8c40bc38a7ed040f175785b48f919c1..bade74310e9d7e4905ca713110ebba852ad9b77f 100644 (file)
@@ -45,6 +45,7 @@ const OstreeCmdPrivateVTable *
 ostree_cmd__private__ (void)
 {
   static OstreeCmdPrivateVTable table = {
+    _ostree_impl_system_generator,
     impl_ostree_generate_grub2_config,
     _ostree_repo_static_delta_dump,
     _ostree_repo_static_delta_query_exists,
index 8d1c653e0b438d7a3804237f04e9eef0edcc7125..63c427cda4cdf27274e1fcc714b90ac918e32d73 100644 (file)
 
 G_BEGIN_DECLS
 
+gboolean _ostree_impl_system_generator (const char *ostree_cmdline, const char *normal_dir, const char *early_dir, const char *late_dir, GError **error);
+
 typedef struct {
+  gboolean (* ostree_system_generator) (const char *ostree_cmdline, const char *normal_dir, const char *early_dir, const char *late_dir, GError **error);
   gboolean (* ostree_generate_grub2_config) (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, GError **error);
   gboolean (* ostree_static_delta_dump) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
   gboolean (* ostree_static_delta_query_exists) (OstreeRepo *repo, const char *delta_id, gboolean *out_exists, GCancellable *cancellable, GError **error);
diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c
new file mode 100644 (file)
index 0000000..7c4d49d
--- /dev/null
@@ -0,0 +1,219 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2017 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <glib-unix.h>
+#include <gio/gunixoutputstream.h>
+#include <errno.h>
+#include <stdio.h>
+#ifdef HAVE_LIBMOUNT
+#include <libmount.h>
+#endif
+#include <stdbool.h>
+#include "otutil.h"
+
+#include "ostree.h"
+#include "ostree-core-private.h"
+#include "ostree-cmdprivate.h"
+
+#ifdef HAVE_LIBMOUNT
+typedef FILE OtLibMountFile;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(OtLibMountFile, endmntent);
+
+/* Taken from systemd path-util.c */
+static bool
+is_path (const char *p)
+{
+  return !!strchr (p, '/');
+}
+
+/* Taken from systemd path-util.c */
+static char*
+path_kill_slashes (char *path)
+{
+  char *f, *t;
+  bool slash = false;
+
+  /* Removes redundant inner and trailing slashes. Modifies the
+   * passed string in-place.
+   *
+   * For example: ///foo///bar/ becomes /foo/bar
+   */
+
+  for (f = path, t = path; *f; f++)
+    {
+      if (*f == '/')
+        {
+          slash = true;
+          continue;
+        }
+
+      if (slash)
+        {
+          slash = false;
+          *(t++) = '/';
+        }
+
+      *(t++) = *f;
+    }
+
+  /* Special rule, if we are talking of the root directory, a
+     trailing slash is good */
+
+  if (t == path && slash)
+    *(t++) = '/';
+
+  *t = 0;
+  return path;
+}
+
+/* Written by ostree-sysroot-deploy.c. We parse out the stateroot here since we
+ * need to know it to mount /var. Unfortunately we can't easily use the
+ * libostree API to find the booted deployment since /boot might not have been
+ * mounted yet.
+ */
+static char *
+stateroot_from_ostree_cmdline (const char *ostree_cmdline,
+                               GError **error)
+{
+  static GRegex *regex;
+  static gsize regex_initialized;
+  if (g_once_init_enter (&regex_initialized))
+    {
+      regex = g_regex_new ("^/ostree/boot.[01]/([^/]+)/", 0, 0, NULL);
+      g_assert (regex);
+      g_once_init_leave (&regex_initialized, 1);
+    }
+
+  g_autoptr(GMatchInfo) match = NULL;
+  if (!g_regex_match (regex, ostree_cmdline, 0, &match))
+    return glnx_null_throw (error, "Failed to parse %s", ostree_cmdline);
+
+  return g_match_info_fetch (match, 1);
+}
+#endif
+
+/* Implementation of ostree-system-generator */
+gboolean
+_ostree_impl_system_generator (const char *ostree_cmdline,
+                               const char *normal_dir,
+                               const char *early_dir,
+                               const char *late_dir,
+                               GError    **error)
+{
+#ifdef HAVE_LIBMOUNT
+  /* Not currently cancellable, but define a var in case we care later */
+  GCancellable *cancellable = NULL;
+  /* Some path constants to avoid typos */
+  static const char fstab_path[] = "/etc/fstab";
+  static const char var_path[] = "/var";
+
+  /* ostree-prepare-root was patched to write the stateroot to this file */
+  g_autofree char *stateroot = stateroot_from_ostree_cmdline (ostree_cmdline, error);
+  if (!stateroot)
+    return FALSE;
+
+  /* Load /etc/fstab if it exists, and look for a /var mount */
+  g_autoptr(OtLibMountFile) fstab = setmntent (fstab_path, "re");
+  gboolean found_var_mnt = FALSE;
+  if (!fstab)
+    {
+      if (errno != ENOENT)
+        return glnx_throw_errno_prefix (error, "Reading %s", fstab_path);
+    }
+  else
+    {
+      /* Parse it */
+      struct mntent *me;
+      while ((me = getmntent (fstab)))
+        {
+          g_autofree char *where = g_strdup (me->mnt_dir);
+          if (is_path (where))
+            path_kill_slashes (where);
+
+          /* We're only looking for /var here */
+          if (strcmp (where, var_path) != 0)
+            continue;
+
+          found_var_mnt = TRUE;
+          break;
+        }
+    }
+
+  /* If we found /var, we're done */
+  if (found_var_mnt)
+    return TRUE;
+
+  /* Prepare to write to the output unit dir; we use the "normal" dir
+   * that overrides /usr, but not /etc.
+   */
+  glnx_fd_close int normal_dir_dfd = -1;
+  if (!glnx_opendirat (AT_FDCWD, normal_dir, TRUE, &normal_dir_dfd, error))
+    return FALSE;
+
+  /* Generate our bind mount unit */
+  const char *stateroot_var_path = glnx_strjoina ("/sysroot/ostree/deploy/", stateroot, "/var");
+
+  glnx_fd_close int tmpfd = -1;
+  g_autofree char *tmppath = NULL;
+  if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY,
+                                      &tmpfd, &tmppath, error))
+    return FALSE;
+  g_autoptr(GOutputStream) outstream = g_unix_output_stream_new (tmpfd, FALSE);
+  gsize bytes_written;
+  /* This code is inspired by systemd's fstab-generator.c.
+   *
+   * Note that our unit doesn't run if systemd.volatile is enabled;
+   * see https://github.com/ostreedev/ostree/pull/856
+   */
+  if (!g_output_stream_printf (outstream, &bytes_written, cancellable, error,
+                               "##\n# Automatically generated by ostree-system-generator\n##\n\n"
+                               "[Unit]\n"
+                               "Documentation=man:ostree(1)\n"
+                               "ConditionKernelCommandLine=!systemd.volatile\n"
+                               /* We need /sysroot mounted writable first */
+                               "After=ostree-remount.service\n"
+                               "Before=local-fs.target\n"
+                               "\n"
+                               "[Mount]\n"
+                               "Where=%s\n"
+                               "What=%s\n"
+                               "Options=bind\n",
+                               var_path,
+                               stateroot_var_path))
+    return FALSE;
+  if (!g_output_stream_flush (outstream, cancellable, error))
+    return FALSE;
+  g_clear_object (&outstream);
+  /* It should be readable */
+  if (fchmod (tmpfd, 0644) < 0)
+    return glnx_throw_errno_prefix (error, "fchmod");
+  /* Error out if somehow it already exists, that'll help us debug conflicts */
+  if (!glnx_link_tmpfile_at (normal_dir_dfd, GLNX_LINK_TMPFILE_NOREPLACE,
+                             tmpfd, tmppath, normal_dir_dfd,
+                             "var.mount", error))
+    return FALSE;
+
+  return TRUE;
+#else
+  return glnx_throw (error, "Not implemented");
+#endif
+}
index b4a8ec0c97ca1330865a5b651358b052a452bb4c..257a058b4161c910f0b5425796f0ce01d5cea3b8 100644 (file)
@@ -1379,6 +1379,7 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
 
   val = ostree_bootconfig_parser_get (bootconfig, "options");
 
+  /* Note this is parsed in ostree-impl-system-generator.c */
   g_autofree char *ostree_kernel_arg = g_strdup_printf ("ostree=/ostree/boot.%d/%s/%s/%d",
                                        new_bootversion, osname, bootcsum,
                                        ostree_deployment_get_bootserial (deployment));
index b24aa44d2518fc7f79b1c8ffee16bcbc03398f62..1e7253d2d83357d29e1f6f748b822a8deaba9f6d 100644 (file)
 #include <err.h>
 #include <stdlib.h>
 #include <sys/statvfs.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
 
 static inline int
 path_is_on_readonly_fs (char *path)
@@ -37,4 +41,66 @@ path_is_on_readonly_fs (char *path)
   return (stvfsbuf.f_flag & ST_RDONLY) != 0;
 }
 
+static inline char *
+read_proc_cmdline (void)
+{
+  FILE *f = fopen("/proc/cmdline", "r");
+  char *cmdline = NULL;
+  size_t len;
+
+  if (!f)
+    goto out;
+
+  /* Note that /proc/cmdline will not end in a newline, so getline
+   * will fail unelss we provide a length.
+   */
+  if (getline (&cmdline, &len, f) < 0)
+    goto out;
+  /* ... but the length will be the size of the malloc buffer, not
+   * strlen().  Fix that.
+   */
+  len = strlen (cmdline);
+
+  if (cmdline[len-1] == '\n')
+    cmdline[len-1] = '\0';
+out:
+  if (f)
+    fclose (f);
+  return cmdline;
+}
+
+static inline char *
+read_proc_cmdline_ostree (void)
+{
+  char *cmdline = NULL;
+  const char *iter;
+  char *ret = NULL;
+
+  cmdline = read_proc_cmdline ();
+  if (!cmdline)
+    err (EXIT_FAILURE, "failed to read /proc/cmdline");
+
+  iter = cmdline;
+  while (iter != NULL)
+    {
+      const char *next = strchr (iter, ' ');
+      const char *next_nonspc = next;
+      while (next_nonspc && *next_nonspc == ' ')
+        next_nonspc += 1;
+      if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0)
+        {
+          const char *start = iter + strlen ("ostree=");
+          if (next)
+            ret = strndup (start, next - start);
+          else
+            ret = strdup (start);
+          break;
+        }
+      iter = next_nonspc;
+    }
+
+  free (cmdline);
+  return ret;
+}
+
 #endif /* __OSTREE_MOUNT_UTIL_H_ */
index 1dac984d238f39af8195e106b4087b76f2140742..15dbafdf5081292380aed9a8b0198a20fb7aeaba 100644 (file)
 
 #include "ostree-mount-util.h"
 
-static char *
-read_proc_cmdline (void)
-{
-  FILE *f = fopen("/proc/cmdline", "r");
-  char *cmdline = NULL;
-  size_t len;
-
-  if (!f)
-    goto out;
-
-  /* Note that /proc/cmdline will not end in a newline, so getline
-   * will fail unelss we provide a length.
-   */
-  if (getline (&cmdline, &len, f) < 0)
-    goto out;
-  /* ... but the length will be the size of the malloc buffer, not
-   * strlen().  Fix that.
-   */
-  len = strlen (cmdline);
-
-  if (cmdline[len-1] == '\n')
-    cmdline[len-1] = '\0';
-out:
-  if (f)
-    fclose (f);
-  return cmdline;
-}
-
-static char *
-parse_ostree_cmdline (void)
-{
-  char *cmdline = NULL;
-  const char *iter;
-  char *ret = NULL;
-
-  cmdline = read_proc_cmdline ();
-  if (!cmdline)
-    err (EXIT_FAILURE, "failed to read /proc/cmdline");
-
-  iter = cmdline;
-  while (iter != NULL)
-    {
-      const char *next = strchr (iter, ' ');
-      const char *next_nonspc = next;
-      while (next_nonspc && *next_nonspc == ' ')
-        next_nonspc += 1;
-      if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0)
-        {
-          const char *start = iter + strlen ("ostree=");
-          if (next)
-            ret = strndup (start, next - start);
-          else
-            ret = strdup (start);
-          break;
-        }
-      iter = next_nonspc;
-    }
-
-  free (cmdline);
-  return ret;
-}
-
 /* This is an API for other projects to determine whether or not the
  * currently running system is ostree-controlled.
  */
@@ -132,7 +70,7 @@ resolve_deploy_path (const char * root_mountpoint)
   struct stat stbuf;
   char *ostree_target, *deploy_path;
 
-  ostree_target = parse_ostree_cmdline ();
+  ostree_target = read_proc_cmdline_ostree ();
   if (!ostree_target)
     errx (EXIT_FAILURE, "No OSTree target; expected ostree=/ostree/boot.N/...");
 
@@ -211,9 +149,12 @@ main(int argc, char *argv[])
   if (chdir (deploy_path) < 0)
     err (EXIT_FAILURE, "failed to chdir to deploy_path");
 
+  /* In the systemd case, this is handled by ostree-system-generator */
+#ifndef HAVE_SYSTEMD_AND_LIBMOUNT
   /* Link to the deployment's /var */
   if (mount ("../../var", "var", NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
     err (EXIT_FAILURE, "failed to bind mount ../../var to var");
+#endif
 
   /* If /boot is on the same partition, use a bind mount to make it visible
    * at /boot inside the deployment. */
diff --git a/src/switchroot/ostree-system-generator.c b/src/switchroot/ostree-system-generator.c
new file mode 100644 (file)
index 0000000..e7205b5
--- /dev/null
@@ -0,0 +1,67 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2017 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <err.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include "ostree-cmdprivate.h"
+#include "ostree-mount-util.h"
+
+static const char *arg_dest = "/tmp";
+static const char *arg_dest_late = "/tmp";
+
+/* This program is a simple stub that calls the implementation that
+ * lives inside libostree.
+ */
+int
+main(int argc, char *argv[])
+{
+  /* Important: if this isn't an ostree-booted system, do nothing; people could
+   * have the package installed as a dependency for flatpak or whatever.
+   */
+  { struct stat stbuf;
+    if (fstatat (AT_FDCWD, "/run/ostree-booted", &stbuf, 0) < 0)
+      exit (EXIT_SUCCESS);
+  }
+
+  if (argc > 1 && argc != 4)
+    errx (EXIT_FAILURE, "This program takes three or no arguments");
+
+  if (argc > 1)
+    arg_dest = argv[1];
+  if (argc > 3)
+    arg_dest_late = argv[3];
+
+  char *ostree_cmdline = read_proc_cmdline_ostree ();
+  if (!ostree_cmdline)
+    errx (EXIT_FAILURE, "Failed to find ostree= kernel argument");
+
+  { g_autoptr(GError) local_error = NULL;
+    if (!ostree_cmd__private__()->ostree_system_generator (ostree_cmdline, arg_dest, NULL, arg_dest_late, &local_error))
+      errx (EXIT_FAILURE, "%s", local_error->message);
+  }
+
+  exit (EXIT_SUCCESS);
+}